package com.appspot.chooserepository.client;

import java.util.ArrayList;
import java.util.Date;

import com.appspot.chooserepository.shared.RepositoryUser;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Repositories_Table implements EntryPoint {

	private static final int ADD_ENTRY = 1;
	private static final int REMOVE_ENTRY = 2;
	private static final int REFRESH = 3;
	private static final int REFRESH_INTERVAL = 1000*10; // ms = 10 sec
	private static final int LONG_DURATION_INTERVAL = 5;
	private static final int OUT_OF_DATE_INTERVAL = 12;
	private VerticalPanel mainPanel = new VerticalPanel();
	private FlexTable repositoriesFlexTable = new FlexTable();
	private HorizontalPanel addPanel = new HorizontalPanel();
	private ListBox usersComboBox = new ListBox();
	private ListBox repositoriesComboBox = new ListBox();
	private Button addButton = new Button("Add");
	private Label lastUpdatedLabel = new Label();
	private Label upToDateLabel = new Label();
	private Label longDurationLabel = new Label();
	private Label outOfDateLabel = new Label();
	private RepositoryUsers repositoryUsers = new RepositoryUsers();
	private RepositoryUserServiceAsync repositoryUserSvc = GWT.create(RepositoryUserService.class);
	private Label errorMsgLabel = new Label();

	/**
	 * Entry point method.
	 */
	public void onModuleLoad() {
		// Create table for repositories.
		repositoriesFlexTable.setText(0, 0, "User");
		repositoriesFlexTable.setText(0, 1, "Last update");
		repositoriesFlexTable.setText(0, 2, "Repositoy");
		repositoriesFlexTable.setText(0, 3, "Remove");

		// Add styles to elements in the repositories table.
		repositoriesFlexTable.setCellPadding(6);

		// Add styles to elements in the repositories table.
		repositoriesFlexTable.getRowFormatter().addStyleName(0, "repositoriesListHeader");
		repositoriesFlexTable.addStyleName("repositoriesList");
		repositoriesFlexTable.getCellFormatter().addStyleName(0, 1,
				"repositoriesListNumericColumn");
		repositoriesFlexTable.getCellFormatter().addStyleName(0, 3,
				"repositoriesListRemoveColumn");

		// Assemble Users ComboBox.
		usersComboBox.setVisibleItemCount(1);
		ArrayList<String> usersList = Users.getListOfUsers();
		for (String userName : usersList) {
			usersComboBox.addItem(userName);
		}
		
		// Assemble Repositories ComboBox.
		repositoriesComboBox.setVisibleItemCount(1);
		ArrayList<String> repositoriesList = Repositories.getListOfRepositories();
		for (String repositoryName : repositoriesList) {
			repositoriesComboBox.addItem(repositoryName);
		}	
		
		// Assemble Add entry panel.
		addPanel.add(usersComboBox);
		addPanel.add(repositoriesComboBox);
		addPanel.add(addButton);
		addPanel.addStyleName("addPanel");

		// Assemble Main panel.
		errorMsgLabel.setStyleName("errorMessage");
		errorMsgLabel.setVisible(false);
		
		// Assemble legend.
		upToDateLabel.setText("0-" + LONG_DURATION_INTERVAL + " hours old entries are colored green");
		longDurationLabel.setText(LONG_DURATION_INTERVAL + "-" + OUT_OF_DATE_INTERVAL + " hours old entries are colored blue");
		outOfDateLabel.setText("More then " + OUT_OF_DATE_INTERVAL + " hours old entries are colored red");
		upToDateLabel.setStyleName("upToDate");
		longDurationLabel.setStyleName("longDuration");
		outOfDateLabel.setStyleName("outOfDate");

		mainPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		mainPanel.setStyleName("mainPanel");
		mainPanel.add(errorMsgLabel);
		mainPanel.add(repositoriesFlexTable);
		mainPanel.add(addPanel);
		mainPanel.add(lastUpdatedLabel);
		lastUpdatedLabel.setHeight("30px");
		mainPanel.add(upToDateLabel);
		mainPanel.add(longDurationLabel);
		mainPanel.add(outOfDateLabel);

		// Associate the Main panel with the HTML host page.
		RootPanel.get("repositoriesList").add(mainPanel);

		// Move cursor focus to the users ComboBox.
		usersComboBox.setFocus(true);

		refreshRepositoriesList();
		
		// Setup timers to refresh list automatically.
		Timer refreshTimer = new Timer() {
			@Override
			public void run() {
				syncManager(REFRESH);
			}
		};
		refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

		// Listen for mouse events on the Add button.
		addButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				syncManager(ADD_ENTRY);
			}
		});
	}
	
	private void syncManager(int action) {
		syncManager(action,null);
	}
	
	private synchronized void syncManager(int action, RepositoryUser user) {
		if (action == ADD_ENTRY) {
			addEntry();
		}
		if (action == REMOVE_ENTRY) {
			removeEntry(user);
		}
		if (action == REFRESH) {
			refreshRepositoriesList();
		}	
	}

	private void addEntry() {
		final String user = usersComboBox.getItemText(usersComboBox.getSelectedIndex());
		final String repository = repositoriesComboBox.getItemText(repositoriesComboBox.getSelectedIndex());
		usersComboBox.setFocus(true);

		// Don't add table entry if repository is taken.
		String otherUser = repositoryUsers.getUsingUser(repository);
		if (otherUser != null) {
			errorMsgLabel.setText("Error: " + repository + " is already taken by " + otherUser + " .");
			errorMsgLabel.setVisible(true);
			return;
		}
		
		final RepositoryUser newUser = new RepositoryUser(user, repository, new Date());//TODO ALON Save email
		repositoryUsers.addRepositoryUser(newUser);
		
		// Initialize the service proxy.
		if (repositoryUserSvc == null) {
			repositoryUserSvc = GWT.create(RepositoryUserService.class);
		}
		
		// Set up the callback object.
		AsyncCallback<Date> callback = new AsyncCallback<Date>() {
			@Override
			public void onFailure(Throwable caught) {
				repositoryUsers.removeRepositoryUser(newUser);
				Window.alert("Error: sync problem, please try again");
			}

			@Override
			public void onSuccess(Date result) {
				if (result == null) {
					repositoryUsers.removeRepositoryUser(newUser);
					Window.alert("Error: sync problem, please try again");
					refreshRepositoriesList();
				} else {
					refreshTimestamp();
					repositoryUsers.setChangeDate(result);
					successAddEntry(newUser);
				}
			}		
		};

		repositoryUserSvc.updateRepositoryUsersList(repositoryUsers, callback);		
	}
	
	private void removeEntry(final RepositoryUser user) {
		final int removedIndex = repositoryUsers.getRepositoryUserIndex(user);
		if (!repositoryUsers.removeRepositoryUser(removedIndex)) {
			Window.alert("Error: sync problem, please try again");
			refreshRepositoriesList();
			return;
		}
		
		// Initialize the service proxy.
		if (repositoryUserSvc == null) {
			repositoryUserSvc = GWT.create(RepositoryUserService.class);
		}
		
		// Set up the callback object.
		AsyncCallback<Date> callback  = new AsyncCallback<Date>() {
			@Override
			public void onFailure(Throwable caught) {
				repositoryUsers.addRepositoryUser(user);
				Window.alert("Error: sync problem, please try again");
			}

			@Override
			public void onSuccess(Date result) {
				refreshTimestamp();
				repositoryUsers.setChangeDate(result);
				repositoriesFlexTable.removeRow(removedIndex + 1);
				// Clear any errors.
				errorMsgLabel.setVisible(false);
			}
	
		};
		
		repositoryUserSvc.updateRepositoryUsersList(repositoryUsers, callback);	
	}
	
	private synchronized void successAddEntry(final RepositoryUser newUser) {
		int row = repositoriesFlexTable.getRowCount();
		for (int i = 1; i < row; i++) {
			String user = repositoriesFlexTable.getText(i, 0);
			String repository = repositoriesFlexTable.getText(i, 2);
			if (newUser.getUser().equals(user) && newUser.getRepository().equals(repository)) {
				return;
			}
		}
		
		// Add the new entry to the table.
		repositoriesFlexTable.setText(row, 0, newUser.getUser());
		repositoriesFlexTable.setText(row, 2, newUser.getRepository());
		Label timeLabel = new Label(DateTimeFormat.getShortDateTimeFormat().format(newUser.getLastUpdated()));
		timeLabel.setStyleName("upToDate");
		repositoriesFlexTable.setWidget(row, 1, timeLabel);
		repositoriesFlexTable.getCellFormatter().addStyleName(row, 1,
				"repositoriesListNumericColumn");
		repositoriesFlexTable.getCellFormatter().addStyleName(row, 3,
				"repositoriesListRemoveColumn");

		// Add a button to remove this entry from the table.
		Button removeEntryButton = new Button("x");
		removeEntryButton.addStyleDependentName("remove");
		removeEntryButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				syncManager(REMOVE_ENTRY,newUser);
			}
		});
		repositoriesFlexTable.setWidget(row, 3, removeEntryButton);
		// Clear any errors.
		errorMsgLabel.setVisible(false);
	}	
	
	private long getHoursInMS(int hours) {
		return 1000*60*60*hours;
	}
	
	/**
	 * Check if entries in table are up to date.
	 */
	private void checkLastUpdated() {
		Date currentDate = new Date();
		int i=0;
		for (Date lastUpdated : repositoryUsers.getLastUpdatedList()) {
			Long offset = currentDate.getTime() - lastUpdated.getTime();
			if (offset > getHoursInMS(OUT_OF_DATE_INTERVAL)) {
				int row = i + 1;
				Label changeWidget = (Label) repositoriesFlexTable.getWidget(row, 1);
				changeWidget.setStyleName("outOfDate");
			} else if (offset > getHoursInMS(LONG_DURATION_INTERVAL)) {
				int row = i + 1;
				Label changeWidget = (Label) repositoriesFlexTable.getWidget(row, 1);
				changeWidget.setStyleName("longDuration");
			}
			i++;
		}
	}

	/**
	 * Get repositories list from server.
	 */
	private void refreshRepositoriesList() {
		// Initialize the service proxy.
		if (repositoryUserSvc == null) {
			repositoryUserSvc = GWT.create(RepositoryUserService.class);
		}
		
		// Set up the callback object.
		AsyncCallback<RepositoryUsers> callback  = new AsyncCallback<RepositoryUsers>() {
			@Override
			public void onFailure(Throwable caught) {
				errorMsgLabel.setText("Error: Refresh failed");
				errorMsgLabel.setVisible(true);	
			}

			@Override
			public void onSuccess(RepositoryUsers result) {
				refreshTimestamp();
				if (result != null) {
					updateTable(result);
				}
				checkLastUpdated();
			}		
		};
	
		repositoryUserSvc.getRepositoryUsersList(callback);
	}

	/**
	 * Update the repositories table and the repositories list.
	 */
	private synchronized void updateTable(RepositoryUsers repositoryUsers) {
		if (this.repositoryUsers.isUpToDate(repositoryUsers.getChangeDate())) {
			return;
		}
		
		this.repositoryUsers = repositoryUsers;
		for (int i = 1; i < repositoriesFlexTable.getRowCount(); i++) {
			repositoriesFlexTable.removeRow(i);
		}
		for (int i = 0; i < repositoryUsers.size(); i++) {	
			successAddEntry(repositoryUsers.getRepositoryUser(i));
		}
	}
	
	private void refreshTimestamp() {
		// Display timestamp showing last refresh.
		lastUpdatedLabel.setText("Last update : "
				+ DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
	}

}
